#include "pvm/pvmhelperapi.h"
#include "lib/pvmsystem.h"
#include "lib/decoders/speex/pvmvoicespeex.h"

#include "lib/memory/pvmmemory.h"

static const int BLOCK_SIZE=200;

void* PVMVoiceSpeex::process_header(ogg_packet *op, spx_int32_t enh_enabled, spx_int32_t *frame_size, int *granule_frame_size, spx_int32_t *rate, int *nframes, int forceMode, int *channels, SpeexStereoState *stereo, int *extra_headers)
{
	void *st;
	const SpeexMode *mode;
	SpeexHeader *header;
	int modeID;
	SpeexCallback callback;

	header = speex_packet_to_header((char*)op->packet, op->bytes);
	if (!header)
	{
		return NULL;
	}
	if (header->mode >= SPEEX_NB_MODES || header->mode<0)
	{
		PVMMemoryFree(header);
		return NULL;
	}

	modeID = header->mode;
	if (forceMode!=-1)
		modeID = forceMode;

	mode = speex_lib_get_mode (modeID);

	if (header->speex_version_id > 1)
	{
		PVMMemoryFree(header);
		return NULL;
	}

	if (mode->bitstream_version < header->mode_bitstream_version)
	{
		PVMMemoryFree(header);
		return NULL;
	}
	if (mode->bitstream_version > header->mode_bitstream_version) 
	{
		PVMMemoryFree(header);
		return NULL;
	}

	st = speex_decoder_init(mode);
	if (!st)
	{
		PVMMemoryFree(header);
		return NULL;
	}
	speex_decoder_ctl(st, SPEEX_SET_ENH, &enh_enabled);
	speex_decoder_ctl(st, SPEEX_GET_FRAME_SIZE, frame_size);
	*granule_frame_size = *frame_size;

	if (!*rate)
		*rate = header->rate;
	/* Adjust rate if --force-* options are used */
	if (forceMode!=-1)
	{
		if (header->mode < forceMode)
		{
			*rate <<= (forceMode - header->mode);
			*granule_frame_size >>= (forceMode - header->mode);
		}
		if (header->mode > forceMode)
		{
			*rate >>= (header->mode - forceMode);
			*granule_frame_size <<= (header->mode - forceMode);
		}
	}


	speex_decoder_ctl(st, SPEEX_SET_SAMPLING_RATE, rate);

	*nframes = header->frames_per_packet;

	if (*channels==-1)
		*channels = header->nb_channels;

	if (!(*channels==1))
	{
		*channels = 2;
		callback.callback_id = SPEEX_INBAND_STEREO;
		callback.func = speex_std_stereo_request_handler;
		callback.data = stereo;
		speex_decoder_ctl(st, SPEEX_SET_HANDLER, &callback);
	}

	*extra_headers = header->extra_headers;

	PVMMemoryFree(header);
	return st;
}

PVMVoiceSpeex::PVMVoiceSpeex()
{
	page_granule = 0;
	last_granule = 0;

	skip_samples = 0;
	page_nb_packets = 0;
	speex_serialno = -1;
	frame_size = 0;
	granule_frame_size = 0;
	nframes = 2;
	extra_headers = 0;
	packet_count = 0;
	enh_enabled = 0;
	rate = 0;
	forceMode = -1;
	channels = -1;
	st = NULL;
	lookahead = 0;
	packet_no = -1;
	stream_init = false;

	outBuffer = NULL;
	samples_copied = 0;

	stream = NULL;
	isInitialized = false;
	closeStream = false;

	loopCount = 1;
	decodeError = false;
	paused = false;

	loopStart = loopEnd = -1;

	streamIsUsed = NULL;
	samples_decoded = 0;
	firstDecode = true;

	eos = false;
}

PVMVoiceSpeex::~PVMVoiceSpeex()
{
	if(stream!=NULL&&streamIsUsed==NULL)
	{
		stream->Close();
	}
	else if(streamIsUsed!=NULL)
	{
		*streamIsUsed = false;
	}

	if (st)
		speex_decoder_destroy(st);

	speex_bits_destroy(&bits);
	if (stream_init)
		ogg_stream_clear(&os);
	ogg_sync_clear(&oy);

	PVMMemoryFree(outBuffer); outBuffer = NULL;
}

pvm_bool PVMVoiceSpeex::Init(IPvmInStream* stream, pvm_bool closeStream)
{
	pvm_bool eos = false;
	ogg_sync_init(&oy);
	speex_bits_init(&bits);
	pvm_bool runFlag  = true;
	isInitialized = false;
	while(runFlag)
	{
		char* data;
		int nb_read;
		data = ogg_sync_buffer(&oy, BLOCK_SIZE);
		//Read bitstream from input file
		nb_read = stream->Read(data, BLOCK_SIZE);//fread(data, sizeof(char), 200, fin);      
		if(nb_read<=0)
			break;
		ogg_sync_wrote(&oy, nb_read);

		while(runFlag && ogg_sync_pageout(&oy, &og)==1)
		{
			if (!stream_init) {
				ogg_stream_init(&os, ogg_page_serialno(&og));
				stream_init = true;
			}
			if (ogg_page_serialno(&og) != os.serialno) {
				/* so all streams are read. */
				ogg_stream_reset_serialno(&os, ogg_page_serialno(&og));
			}
			/*Add page to the bitstream*/
			ogg_stream_pagein(&os, &og);
			page_granule = ogg_page_granulepos(&og);
			page_nb_packets = ogg_page_packets(&og);
			if (page_granule>0 && frame_size)
			{
				/* FIXME: shift the granule values if --force-* is specified */
				skip_samples = (int)(frame_size*(page_nb_packets*granule_frame_size*nframes - (page_granule-last_granule))/granule_frame_size);
				if (ogg_page_eos(&og))
					skip_samples = -skip_samples;
				/*else if (!ogg_page_bos(&og))
				skip_samples = 0;*/
			}
			else
			{
				skip_samples = 0;
			}
			last_granule = page_granule;
			packet_no=0;
			while (!eos && ogg_stream_packetout(&os, &op) == 1)
			{
				if (op.bytes>=5 && !memcmp(op.packet, "Speex", 5)) {
					speex_serialno = os.serialno;
				}
				if (speex_serialno == -1 || os.serialno != speex_serialno)
					break;

				if (packet_count==0)
				{
					st = process_header(&op, enh_enabled, &frame_size, &granule_frame_size, &rate, &nframes, forceMode, &channels, &stereo, &extra_headers);
					if (!st)
					{
						isInitialized = false;
						runFlag = false;
						break;
					}
					speex_decoder_ctl(st, SPEEX_GET_LOOKAHEAD, &lookahead);
					if (!nframes)
						nframes=1;

					isInitialized = true;
					runFlag = false;
					packet_count++;
					break;
				}
				else
				{
					packet_count++;
				}
			}
		}
	}

	if(isInitialized)
	{
		this->stream = stream;
		this->closeStream = closeStream;
	}
	return isInitialized;
}

PVMVoiceSpeex* PVMVoiceSpeex::Create(IPvmInStream* srcStream, pvm_bool copyStream, pvm_bool* streamIsUsed)
{
	PVMVoiceSpeex* voice = new PVMVoiceSpeex();
	IPvmInStream* myStream = NULL;
	if(copyStream)
	{
		myStream = srcStream->OpenCopy();
	}
	else
	{
		myStream = srcStream;
	}

	if(myStream==NULL)
		return NULL;

	if(!voice->Init(myStream, copyStream))
	{
		delete( voice);
		if(copyStream)
			myStream->Close();
		return NULL;
	}
	voice->outBufferSize = MAX_FRAME_SIZE;
	voice->outBuffer = (pvm_int16*)PVMMemoryAlloc(sizeof(pvm_int16) * voice->outBufferSize);
	voice->streamIsUsed = streamIsUsed;
	return voice;
}

void PVMVoiceSpeex::GetFormat(pvm_uint32 &samplesPerSecond, pvm_uint32 &bitsperSample, pvm_uint32 &chanels)
{
	samplesPerSecond = rate;
	bitsperSample = 16;
	chanels = channels;
}

pvm_uint32 PVMVoiceSpeex::GetSamplesPerSecond()
{
	return rate;
}
pvm_uint32 PVMVoiceSpeex::GetBitsPerSample()
{
	return 16;
}
pvm_uint32 PVMVoiceSpeex::GetChanelsNum()
{
	return channels;
}

pvm_uint32 PVMVoiceSpeex::decodeDataBlock()
{
	samples_decoded = 0;
	if(decodeError)
		return 0;

	switch(firstDecode)
	{
	default:
		while (1)
		{
			char *data;
			int nb_read;
			//Get the ogg buffer for writing
			data = ogg_sync_buffer(&oy, BLOCK_SIZE);
			//Read bitstream from input file
			nb_read = stream->Read(data, BLOCK_SIZE);
			if(nb_read<=0)
			{
				decodeError = true;
				break;
			}
			ogg_sync_wrote(&oy, nb_read);

			//Loop for all complete pages we got (most likely only one)
			while (ogg_sync_pageout(&oy, &og)==1)
			{
				int packet_no;
				if (stream_init == 0) {
					ogg_stream_init(&os, ogg_page_serialno(&og));
					stream_init = 1;
				}
				if (ogg_page_serialno(&og) != os.serialno) {
					// so all streams are read.
					ogg_stream_reset_serialno(&os, ogg_page_serialno(&og));
				}
				//Add page to the bitstream
				ogg_stream_pagein(&os, &og);
				page_granule = ogg_page_granulepos(&og);
				page_nb_packets = ogg_page_packets(&og);
				if (page_granule>0 && frame_size)
				{
					skip_samples = (int)(frame_size*(page_nb_packets*granule_frame_size*nframes - (page_granule-last_granule))/granule_frame_size);
					if (ogg_page_eos(&og))
						skip_samples = -skip_samples;
				} else
				{
					skip_samples = 0;
				}
				last_granule = page_granule;
				//Extract all available packets
				packet_no=0;
				while (!eos && ogg_stream_packetout(&os, &op) == 1)
				{
					if (op.bytes>=5 && !memcmp(op.packet, "Speex", 5)) {
						speex_serialno = os.serialno;
					}
					if (speex_serialno == -1 || os.serialno != speex_serialno)
						break;
					if (packet_count<=1+extra_headers)
					{
						// Ignore extra headers
					} else {
						packet_no++;

						//End of stream condition
						if (op.e_o_s && os.serialno == speex_serialno) // don't care for anything except speex eos
							eos=1;

						//Copy Ogg packet to Speex bitstream
						speex_bits_read_from(&bits, (char*)op.packet, op.bytes);
						for (j=0;j!=nframes;j++)
						{
							int ret;
							//Decode frame
							ret = speex_decode_int(st, &bits, output);

							if (ret==-1)
								break;
							if (ret==-2)
							{
								//Decoding error: corrupted stream?
								break;
							}
							if (speex_bits_remaining(&bits)<0)
							{
								//Decoding overflow: corrupted stream?
								break;
							}
							if (channels==2)
								speex_decode_stereo_int(output, frame_size, &stereo);

							{
								frame_offset = 0;
								new_frame_size = frame_size;
								if (packet_no == 1 && j==0 && skip_samples > 0)
								{
									new_frame_size -= skip_samples+lookahead;
									frame_offset = skip_samples+lookahead;
								}
								if (packet_no == page_nb_packets && skip_samples < 0)
								{
									int packet_length = nframes*frame_size+skip_samples+lookahead;
									new_frame_size = packet_length - j*frame_size;
									if (new_frame_size<0)
										new_frame_size = 0;
									if (new_frame_size>frame_size)
										new_frame_size = frame_size;
								}
								if (new_frame_size>0)
								{
									memcpy(outBuffer, output+frame_offset*channels, sizeof(short) * new_frame_size*channels);
									samples_decoded = new_frame_size*channels;
									firstDecode = false;
									return samples_decoded;
								//	WIN_Play_Samples (out+frame_offset*channels, sizeof(short) * new_frame_size*channels);
								//	audio_size+=sizeof(short)*new_frame_size*channels;
								}
								case false:;
							}
						}
					}
					packet_count++;
				}
			}
			if (stream->Eof())
			{
				if(samples_decoded==0)
					decodeError = true;
				break;
			}
		}
	}

	return samples_decoded;	
}

pvm_uint32 PVMVoiceSpeex::copyDecodedData(void* buffer, pvm_int maxSamplesNum)
{
	if((samples_decoded - samples_copied)>0)
	{
		//copy samples from previous call
		pvm_int samplesToCopy = samples_decoded - samples_copied;
		if(samplesToCopy>maxSamplesNum)
			samplesToCopy = maxSamplesNum;
		memcpy(buffer, outBuffer + samples_copied, samplesToCopy*sizeof(pvm_int16));
		samples_copied += samplesToCopy;

		return samplesToCopy;
	}

	return 0;
}

pvm_uint32 PVMVoiceSpeex::FillBuffer(void *buffer, pvm_uint32 numSamples)
{
	pvm_uint32 samplesFilled = 0;
	//pvm_int16* bufferPtr = (pvm_int16*)buffer;

	while(samplesFilled<numSamples)
	{
		samplesFilled += copyDecodedData(((pvm_int8*)buffer) + samplesFilled*sizeof(pvm_int16), numSamples - samplesFilled);
		if(samplesFilled>=numSamples)
			break;
		if(decodeError)
			return samplesFilled;
		if(decodeDataBlock()<=0)
		{
			break;//no more data
		}
		else
		{
			if(samples_copied>0)
				samples_copied = samples_copied;
			samples_copied = 0;
		}

	}

	return samplesFilled;
}
void PVMVoiceSpeex::SetPause(pvm_bool pause)
{
	this->paused = pause;
}
pvm_bool PVMVoiceSpeex::GetPause()
{
	return paused;
}
pvm_bool PVMVoiceSpeex::IsAlive()
{
	if((samples_decoded - samples_copied)>0)
		return true;

	return decodeError==false;
}
void PVMVoiceSpeex::DoDestroy()
{
	delete( this);
}

void PVMVoiceSpeex::OnIdle()
{

}

void PVMVoiceSpeex::SetLoopCount(pvm_int32 loopCount)
{
	this->loopCount = loopCount;
}

pvm_int32 PVMVoiceSpeex::GetLoopCount()
{
	return loopCount;
}

pvm_bool PVMVoiceSpeex::SetPositionMS(pvm_int32 /*position*/)
{
	return false;
}
pvm_int32 PVMVoiceSpeex::GetPositionMS()
{
	return -1;
}

pvm_bool PVMVoiceSpeex::GetOption(const pvm_char* /*optionName*/, void* /*optionVal*/, pvm_uint32* /*optionSize*/)
{
	return false;
}
pvm_bool PVMVoiceSpeex::SetOption(const pvm_char* /*optionName*/, void* /*optionVal*/, pvm_uint32 /*optionSize*/)
{
	return false;
}
